package com.kx.ssm.controller;

import com.github.pagehelper.PageInfo;
import com.kx.ssm.constants.ResultCode;
import com.kx.ssm.dto.R;
import com.kx.ssm.exception.QueryDataException;
import com.kx.ssm.pojo.User;
import com.kx.ssm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @ClassName UserController
 * @Author Sun
 * @Date 2021/5/20 21:52
 */

@RestController//相当于@Controller和@ResponseBody两个注解
@RequestMapping("/users")
public class UserController {

    private ConcurrentHashMap<String, ArrayList<Long>> failTimesMap = new ConcurrentHashMap<>();
    @Autowired
    private UserService userService;

    @GetMapping("/users")
    public R getUsersByPage(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "5") int pageSize) {
        PageInfo<User> pageInfo = userService.getUsersByPage(pageNum, pageSize);
        return R.setOK(pageInfo);
    }

    @GetMapping("/user/{id}")
    public R getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return R.setOK(user);
    }

    //注册
    @PostMapping("/user")
    public R addUser(User user) {
        userService.addUser(user);
        return R.setOK();
    }

    //登录
    @PostMapping("/login")
    public void Login(@RequestBody User user, HttpSession session, HttpServletRequest request) {

        //先判断是不是频繁的登录
        //如果不是就继续下一步,如果是就直接返回,抛出异常
        //我们发现只判断账号密码的错误此处不足以拦截部分恶意请求,因为发起恶意请求的人可以随意传递不同的账号和密码
        //我们还需要对 ip 进行过滤,当然 ip 的过滤可以通过前置的防火墙进行部分过滤

        String username = user.getUsername();//错误的用户名
        String remoteAddr = request.getRemoteAddr();//获取错误的ip
        ArrayList<Long> longArrayList = failTimesMap.get(username);//同一用户名登录失败次数
        ArrayList<Long> remoteAddrFailList = failTimesMap.get(remoteAddr);//同一ip登录失败的次数

        //在这里进行判断如果用户超过一个小时没有登录应该删除无效的登录错误时候记录的时间
        //先看看当前用户有没有超过失败次数在判断之前需要先过滤掉之前超出时间范围的数据
        //如果当前时间和最后一次登录失败时间差大于1小时，说明前面的数据都是无效数据
        long millis = System.currentTimeMillis();//获取当前登录时间

        if (!(longArrayList == null || longArrayList.size() < 3)) {
            Long aLong = longArrayList.get(longArrayList.size() - 1);//获取最后一次登录失败时间
            if (millis - aLong > 1000 * 60 * 60) {
                //之前登录失败的数据都是无效的，清空之前登录失败数据
                longArrayList.clear();
                failTimesMap.remove(username);
            } else {
                //开始判断longArrayList集合中错误时间超出范围了没
                List<Long> collect = longArrayList.stream()
                        .filter(times -> (millis - times) < 60 * 60 * 1000)
                        .collect(Collectors.toList());

                longArrayList.clear();//清空之前保存错误数据的集合,因为有一些数据无效了

                if (collect == null) {
                    failTimesMap.remove(remoteAddr);
                } else {
                    //只保当前有效数据
                    longArrayList.addAll(collect);
                    if (collect.size() >= 3) {
                        //拦截请求
                        throw new QueryDataException("登录超过失败次数,请稍后再试", ResultCode.USERNAME_PASSOWRD_ERROR_LIMIT);
                    }
                }
            }
        }

        try {

            //TODO 用户用正确的账号和密码恶意发起请求来对我们的数据库进行流量攻击
            User userByNameAndPassword = userService.findUserByNameAndPassword(user);
            //登录成功后把信息存入session中
            session.setAttribute("user", userByNameAndPassword);
        } catch (Exception e) {
            e.printStackTrace();
            //登录失败，需要对失败次数进行限制(通过用户名和ip进行限制)，避免用户发起恶意攻击和撞库行为
            //代表着登录失败,这里能不能获取 账号, 时间和 ip

            /*经过分析,我们知道每次失败时间记录下来,最后总的时间的个数就是失败的次数,
            但是我们对这个时间又有要求,比如昨天的失败不应该影响今天的操作,所以我们不能只是一直增加,
            还要删除过期无效的数据(超出我们时间要求的失败次数)*/
            //1 增加一次失败的记录(增加用户和ip)
            //使用map进行存储，key保存username和ip，value保存失败时间，通过失败时间的个数可以知道失败次数

            longArrayList = failTimesMap.get(username);//同一用户名登录失败次数
            remoteAddrFailList = failTimesMap.get(remoteAddr);//同一ip登录失败的次数
            long currentTimeMillis = System.currentTimeMillis();//当前失败的时间

            if (longArrayList == null) {
                longArrayList = new ArrayList<>();
            }
            longArrayList.add(currentTimeMillis);//添加失败时间
            failTimesMap.put(username, longArrayList);

            if (remoteAddrFailList == null) {
                remoteAddrFailList = new ArrayList<>();
            }
            remoteAddrFailList.add(currentTimeMillis);//添加失败时间
            failTimesMap.put(remoteAddr, remoteAddrFailList);

            //2 删除过期的失败记录(删除用户和 ip)
           /* 经过分析,在这里删除没有什么意义,因为我们是在登录的时候判断用户在指定时间范围内有没有失败超过次数
            如果在这里删除了,然后过了一段时间用户再登录,这里删除剩下的数据可能照样没有任何意义,比如我们允许的时间差为 1小时
            用户登录失败了在这里我们删除了部分数据,但是过了 2 小时候用户登录,其实这个时候所有的数据都是无效的,
            但是上面的判断直接获取长度进行比较就不对了,所以应该是在登录的时候删除
            */
            throw e;//扔掉的目的是让统一的异常处理机制捕获异常返回结果
        }
    }


    /**
     * 更新用户信息
     *
     * @param user
     * @param session
     * @return
     */
    @PutMapping("/user")////我们的更新操作没有写 id,因为如果写了 id 就代表我可以随意更新某个用户的数据,不安全,我们现在通过 session 可以知道当前的登录用户,所以不需要id
    public R updateUser(@RequestBody User user, HttpSession session) {
        if (user == null) {
            return R.setError(ResultCode.DATA_NOT_WANZHENG, "用户数据为空");
        }
        //我们的目的是更新用户,但是我们的更新业务中的判断逻辑是用户已经登录,查询数据并保存在我们的程序内部,已经做了一些其他操作
        //但是现在contrtoller中的user参数是我们按照用户传递的数据封装的,可能会不全,为空,怎么办?
        // 更新操作只能更新自己, 而且要先登录,所以这个时候如果方法能执行说明用户一定是登录的
        User userSession = (User) session.getAttribute("user");//获取登录用户的信息
        //更新用户信息时用户表单中用户不需要填写用户id,所以要从session中获取id
        user.setId(userSession.getId());
        //设置密码的盐
        String passwordSalt = userSession.getPasswordsalt();
        System.out.println("passwordSalt = " + passwordSalt);
        user.setPasswordsalt(passwordSalt);
        userService.updateUser(user);
        return R.setOK();
    }
}
